﻿using System;
using Ewk.Math.ComputationTypes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;

namespace Ewk.Math.Algebra.UnitTests.MatrixTests.FunctionTests
{
    [TestClass]
    public class MatrixFunctionUnitTests : MatrixUnitTestsBase<ComputationTypes.Function<double, double>>
    {
        #region Swapping rows

        [TestMethod]
        public void MatrixFunction_SwapRows_swaps_two_rows_of_a_matrix()
        {
            const int m = 13;
            var matrix = CreateMatrix(m, m, (i, j) => new ComputationTypes.Function<double, double>(x => x + (i + j) * (j + i + 1)));

            const int rowNumber1 = 5;
            const int rowNumber2 = 11;

            var expectedRow1 = matrix.GetRow(rowNumber1);
            var expectedRow2 = matrix.GetRow(rowNumber2);

            matrix.SwapRows(rowNumber1, rowNumber2);

            var row1 = matrix.GetRow(rowNumber1);
            var row2 = matrix.GetRow(rowNumber2);

            Assert.AreEqual(expectedRow2, row1);
            Assert.AreEqual(expectedRow1, row2);
        }

        [TestMethod]
        public void MatrixFunction_SwapRows_results_in_call_to_the_corresponding_determinantManipulator()
        {
            const int rowNumber1 = 2;
            const int rowNumber2 = 3;

            const int m = 5;
            var matrixI = Matrix<ComputationTypes.Function<double, double>>.Identity(m);

            DeterminantComputer
                .Expect(computer =>
                        computer.ComputeDeterminant(Arg<Matrix<ComputationTypes.Function<double, double>>>.Is.Anything))
                .Return(Computable<ComputationTypes.Function<double, double>>.Factory.One)
                .Repeat.Any();
            DeterminantComputer.Replay();

            var expectedDeterminant = matrixI.Determinant * (Computable<ComputationTypes.Function<double, double>>.Factory.Zero - Computable<ComputationTypes.Function<double, double>>.Factory.One);

            DeterminantManipulator
                .Expect(manipulations => manipulations.SwapRows(rowNumber1, rowNumber2))
                .Return(expectedDeterminant);
            DeterminantManipulator.Replay();

            matrixI.SwapRows(rowNumber1, rowNumber2);
            var determinant = matrixI.Determinant;

            Assert.AreEqual(expectedDeterminant, determinant);
            DeterminantManipulator.VerifyAllExpectations();
        }

        [TestMethod]
        public void MatrixFunction_SwapRows_does_not_reset_the_internally_stored_determinant()
        {
            DeterminantComputer
                .Expect(computer =>
                        computer.ComputeDeterminant(Arg<Matrix<ComputationTypes.Function<double, double>>>.Is.Anything))
                .Return(Computable<ComputationTypes.Function<double, double>>.Factory.One)
                .Repeat.Once();
            DeterminantComputer.Replay();

            const int m = 5;
            var matrixI = Matrix<ComputationTypes.Function<double, double>>.Identity(m);

            const int rowNumber1 = 2;
            const int rowNumber2 = 3;

            var det1 = matrixI.Determinant;
            matrixI.SwapRows(rowNumber1, rowNumber2);
            var det2 = matrixI.Determinant;

            Assert.IsInstanceOfType(det1, typeof(Computable<ComputationTypes.Function<double, double>>));
            Assert.IsInstanceOfType(det2, typeof(Computable<ComputationTypes.Function<double, double>>));
            DeterminantComputer.VerifyAllExpectations();
        }

        [TestMethod, ExpectedException(typeof(IndexOutOfRangeException))]
        public void MatrixFunction_SwapRows_throws_an_ArgumentException_when_the_first_index_is_smaller_than_zero()
        {
            const int m = 13;
            var matrixI = Matrix<ComputationTypes.Function<double, double>>.Identity(m);

            const int rowNumber1 = -1;
            const int rowNumber2 = 11;

            matrixI.SwapRows(rowNumber1, rowNumber2);
        }

        [TestMethod, ExpectedException(typeof(IndexOutOfRangeException))]
        public void MatrixFunction_SwapRows_throws_an_ArgumentException_when_the_first_index_is_greater_than_the_number_of_columns()
        {
            const int m = 13;
            var matrixI = Matrix<ComputationTypes.Function<double, double>>.Identity(m);

            const int rowNumber1 = 13;
            const int rowNumber2 = 11;

            matrixI.SwapRows(rowNumber1, rowNumber2);
        }

        [TestMethod, ExpectedException(typeof(IndexOutOfRangeException))]
        public void MatrixFunction_SwapRows_throws_an_ArgumentException_when_the_second_index_is_smaller_than_zero()
        {
            const int m = 13;
            var matrixI = Matrix<ComputationTypes.Function<double, double>>.Identity(m);

            const int rowNumber1 = 5;
            const int rowNumber2 = -1;

            matrixI.SwapRows(rowNumber1, rowNumber2);
        }

        [TestMethod, ExpectedException(typeof(IndexOutOfRangeException))]
        public void MatrixFunction_SwapRows_throws_an_ArgumentException_when_the_second_index_is_greater_than_the_number_of_columns()
        {
            const int m = 13;
            var matrixI = Matrix<ComputationTypes.Function<double, double>>.Identity(m);

            const int rowNumber1 = 5;
            const int rowNumber2 = 13;

            matrixI.SwapRows(rowNumber1, rowNumber2);
        }

        #endregion

        #region Swapping columns

        [TestMethod]
        public void MatrixFunction_SwapColumns_swaps_two_columns_of_a_matrix()
        {
            const int m = 13;
            var matrix = CreateMatrix(m, m, (i, j) => new ComputationTypes.Function<double, double>(x => x + (i + j) * (j + i + 1)));

            const int columnNumber1 = 5;
            const int columnNumber2 = 11;

            var expectedColumn1 = matrix.GetColumn(columnNumber1);
            var expectedColumn2 = matrix.GetColumn(columnNumber2);

            matrix.SwapColumns(columnNumber1, columnNumber2);

            var column1 = matrix.GetColumn(columnNumber1);
            var column2 = matrix.GetColumn(columnNumber2);

            Assert.AreEqual(expectedColumn2, column1);
            Assert.AreEqual(expectedColumn1, column2);
        }

        [TestMethod]
        public void MatrixFunction_SwapColumns_results_in_call_to_the_corresponding_determinantManipulator()
        {
            const int columnNumber1 = 2;
            const int columnNumber2 = 3;

            const int m = 5;
            var matrixI = Matrix<ComputationTypes.Function<double, double>>.Identity(m);

            DeterminantComputer
                .Expect(computer =>
                        computer.ComputeDeterminant(Arg<Matrix<ComputationTypes.Function<double, double>>>.Is.Anything))
                .Return(Computable<ComputationTypes.Function<double, double>>.Factory.One)
                .Repeat.Any();
            DeterminantComputer.Replay();

            var expectedDeterminant = matrixI.Determinant * (Computable<ComputationTypes.Function<double, double>>.Factory.Zero - Computable<ComputationTypes.Function<double, double>>.Factory.One);

            DeterminantManipulator
                .Expect(manipulations => manipulations.SwapColumns(columnNumber1, columnNumber2))
                .Return(expectedDeterminant);
            DeterminantManipulator.Replay();

            matrixI.SwapColumns(columnNumber1, columnNumber2);
            var determinant = matrixI.Determinant;

            Assert.AreEqual(expectedDeterminant, determinant);
            DeterminantManipulator.VerifyAllExpectations();
        }

        [TestMethod]
        public void MatrixFunction_SwapColumns_does_not_reset_the_internally_stored_determinant()
        {
            DeterminantComputer
                .Expect(computer =>
                        computer.ComputeDeterminant(Arg<Matrix<ComputationTypes.Function<double, double>>>.Is.Anything))
                .Return(Computable<ComputationTypes.Function<double, double>>.Factory.One)
                .Repeat.Once();
            DeterminantComputer.Replay();

            const int m = 5;
            var matrixI = Matrix<ComputationTypes.Function<double, double>>.Identity(m);

            const int columnNumber1 = 2;
            const int columnNumber2 = 3;

            var det1 = matrixI.Determinant;
            matrixI.SwapColumns(columnNumber1, columnNumber2);
            var det2 = matrixI.Determinant;

            Assert.IsInstanceOfType(det1, typeof(Computable<ComputationTypes.Function<double, double>>));
            Assert.IsInstanceOfType(det2, typeof(Computable<ComputationTypes.Function<double, double>>));
            DeterminantComputer.VerifyAllExpectations();
        }

        [TestMethod, ExpectedException(typeof(IndexOutOfRangeException))]
        public void MatrixFunction_SwapColumns_throws_an_ArgumentException_when_the_first_index_is_smaller_than_zero()
        {
            const int m = 13;
            var matrixI = Matrix<ComputationTypes.Function<double, double>>.Identity(m);

            const int columnNumber1 = -1;
            const int columnNumber2 = 11;

            matrixI.SwapColumns(columnNumber1, columnNumber2);
        }

        [TestMethod, ExpectedException(typeof(IndexOutOfRangeException))]
        public void MatrixFunction_SwapColumns_throws_an_ArgumentException_when_the_first_index_is_greater_than_the_number_of_columns()
        {
            const int m = 13;
            var matrixI = Matrix<ComputationTypes.Function<double, double>>.Identity(m);

            const int columnNumber1 = 13;
            const int columnNumber2 = 11;

            matrixI.SwapColumns(columnNumber1, columnNumber2);
        }

        [TestMethod, ExpectedException(typeof(IndexOutOfRangeException))]
        public void MatrixFunction_SwapColumns_throws_an_ArgumentException_when_the_second_index_is_smaller_than_zero()
        {
            const int m = 13;
            var matrixI = Matrix<ComputationTypes.Function<double, double>>.Identity(m);

            const int columnNumber1 = 5;
            const int columnNumber2 = -1;

            matrixI.SwapColumns(columnNumber1, columnNumber2);
        }

        [TestMethod, ExpectedException(typeof(IndexOutOfRangeException))]
        public void MatrixFunction_SwapColumns_throws_an_ArgumentException_when_the_second_index_is_greater_than_the_number_of_columns()
        {
            const int m = 13;
            var matrixI = Matrix<ComputationTypes.Function<double, double>>.Identity(m);

            const int columnNumber1 = 5;
            const int columnNumber2 = 13;

            matrixI.SwapColumns(columnNumber1, columnNumber2);
        }

        #endregion
    }
}